package cmd

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"strconv"
	"strings"
	"unicode/utf8"

	"gitee.com/qianyucc/secure-tool/util"
	"github.com/spf13/cobra"
)

var (
	HEX_SET = map[byte]bool{'0': true, '1': true, '2': true, '3': true, '4': true, '5': true, '6': true, '7': true, '8': true, '9': true, 'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true}
	HEX_MAP = map[byte]uint32{'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, 'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15}
)

// unicodeCmd 16进制编码
var unicodeCmd = &cobra.Command{
	Use:   "unicode",
	Short: "Use unicode to encode and decode strings",
	Long:  `Use unicode to encode and decode strings`,
	Run: func(cmd *cobra.Command, args []string) {
		var err error
		if err = cmd.ParseFlags(args); err != nil {
			fmt.Println(err)
			return
		}
		if IsDecode {
			if InputFile == "" {
				if err := DecodeStringWithUnicode(args); err != nil {
					fmt.Println(err)
				}
			} else {
				OutputFile = util.GetOrDefaultString(OutputFile, fmt.Sprintf("%s-unicode-dec.txt", InputFile))
				if err := DecodeFileWithUnicode(InputFile, OutputFile); err != nil {
					fmt.Println(err)
				}
			}
		} else {
			if InputFile == "" {
				if err := EncodeStringWithUnicode(args); err != nil {
					fmt.Println(err)
				}
			} else {
				OutputFile = util.GetOrDefaultString(OutputFile, fmt.Sprintf("%s-unicode-enc.txt", InputFile))
				if err := EncodeFileWithUnicode(InputFile, OutputFile); err != nil {
					fmt.Println(err)
				}
			}
		}
	},
}

// DecodeStringWithUnicode 使用 unicode 解码字符串
func DecodeStringWithUnicode(args []string) (err error) {
	var text string
	if text = util.ReadTextFromClipboard(); len(args) == 0 && text == "" {
		return fmt.Errorf("invalid parameters, please enter the text you want to encode")
	}
	if len(args) > 0 {
		text = args[0]
	}
	var r string
	if r, err = decodeUnicode(text); err != nil {
		return err
	}
	util.PrintAndCopyTextToClipboardIgnoreError(r)
	return nil
}

// EncodeStringWithUnicode 使用 unicode 编码字符串
func EncodeStringWithUnicode(args []string) (err error) {
	var text string
	if text = util.ReadTextFromClipboard(); len(args) == 0 && text == "" {
		return fmt.Errorf("invalid parameters, please enter the text you want to encode")
	}
	if len(args) > 0 {
		text = args[0]
	}
	var r string
	if r, err = encodeUnicode(text); err != nil {
		return err
	}
	util.PrintAndCopyTextToClipboardIgnoreError(r)
	return nil
}

// DecodeFileWithUnicode 使用 unicode 解码文件
func DecodeFileWithUnicode(inputFilePath, outputFilePath string) (err error) {
	var raw []byte
	if raw, err = ioutil.ReadFile(InputFile); err != nil {
		return err
	}
	var r string
	if r, err = decodeUnicode(string(raw)); err != nil {
		return err
	}
	return ioutil.WriteFile(outputFilePath, []byte(r), 0644)
}

// EncodeFileWithUnicode 使用 unicode 编码文件
func EncodeFileWithUnicode(inputFilePath, outputFilePath string) (err error) {
	var raw []byte
	if raw, err = ioutil.ReadFile(InputFile); err != nil {
		return err
	}
	var r string
	if r, err = encodeUnicode(string(raw)); err != nil {
		return err
	}
	return ioutil.WriteFile(outputFilePath, []byte(r), 0644)
}

// decodeUnicode 解码字符串实现
func decodeUnicode(text string) (res string, err error) {
	raw := []byte(text)
	builder := strings.Builder{}
	n := len(raw)
	for i := 0; i < n; i++ {
		ch := raw[i]
		if ch == '\\' && i+1 < n && raw[i+1] == 'u' {
			i += 2
			if i > n {
				break
			}
			start := i
			for i < n && i-start < 8 && HEX_SET[util.ByteToLower(raw[i])] {
				i++
			}
			num, err := hexToUint32(raw[start:i])
			if err != nil {
				return "", err
			}
			builder.WriteRune(rune(num))
			i--
		} else {
			builder.WriteByte(ch)
		}
	}
	return builder.String(), nil
}

// encodeUnicode 编码字符串实现
func encodeUnicode(text string) (res string, err error) {
	builder := strings.Builder{}
	for _, item := range text {
		runeLen := utf8.RuneLen(item)
		if runeLen > 1 {
			asciiStr := strconv.QuoteRuneToASCII(item)
			builder.WriteString(asciiStr[1 : len(asciiStr)-1])
		} else {
			builder.WriteByte(byte(item))
		}
	}
	return builder.String(), nil
}

// hexToUint32 将十六进制编码的字节数组转化为uint32类型的整数
func hexToUint32(raw []byte) (num uint32, err error) {
	n := len(raw)
	if n > 8 {
		return num, fmt.Errorf("invalid unicode length: %d", n)
	}
	raw = bytes.ToLower(raw)
	util.ReverseBytes(raw)
	for i, item := range raw {
		num ^= HEX_MAP[item] << (i * 4)
	}
	return num, nil
}

func init() {
	rootCmd.AddCommand(unicodeCmd)

	unicodeCmd.Flags().StringVarP(&InputFile, "file", "f", "", "The file name you want to encode or decode")
	unicodeCmd.Flags().StringVarP(&OutputFile, "out", "o", "", `The file name after encoding or decoding. 
    When encoding, if you do not enter this flag, the output file will be named as ${fileName}-unicode-enc.txt by default,
    When decoding, if you do not enter this flag, the output file will be named as ${fileName}-unicode-dec.txt by default`)
	unicodeCmd.Flags().BoolVarP(&IsDecode, "decode", "d", false, "Indicates whether it is a decoding operation")
}
